﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using Golem.SearchDatabase.Models;
using System.Threading.Tasks;
using System.Threading;
using System.Data.Linq;
using System.Data.Common;
using System.Transactions;

using data = Golem.Indexer.Data;
using System.Web;

namespace Golem.SearchDatabase
{
    public class MSSQLDatabase : Database
    {
        public override string ConnectionString
        {
            get
            {
                return String.Format("Data Source={0},{1};Initial Catalog={2};Persist Security Info=True;User ID={3};Password={4};Pooling=False", this.Address, this.Port, this.DatabaseName, this.Username, this.Password);
            }
        }

        public MSSQLDatabase(String address, String databaseName, String username, String password, int port = 1433, bool autocreate = false)
            : base(address, port, databaseName, username, password, autocreate)
        {

        }

        public override bool IsDatabaseReady()
        {
            return true;
        }

        public override void InitDatabase()
        {
            using (Models.MSSQLDataContext db = new Models.MSSQLDataContext(this.ConnectionString))
            {
                if (db.DatabaseExists())
                {
                    db.ExecuteCommand("ALTER DATABASE [webSearch] SET OFFLINE WITH ROLLBACK IMMEDIATE; ALTER DATABASE [webSearch] SET ONLINE;");
                    db.DeleteDatabase();
                }

                db.CreateDatabase();

                db.ExecuteScript(global::Golem.SearchDatabase.Properties.Resources.MSSQL_CreateFulltextSearch);
            }
        }

        private Protocol GetProtocol(Models.MSSQLDataContext db, long machineId, String protocolName)
        {
            var fmachine = CompiledQuery.Compile((MSSQLDataContext ldb, long lmachineId, String lprotocolName) => from q in ldb.Protocols where q.MachineId == lmachineId && q.Name == lprotocolName select q);
            return fmachine(db, machineId, protocolName).SingleOrDefault();
        }

        private Machine GetMachine(Models.MSSQLDataContext db, String ip)
        {
            var fmachine = CompiledQuery.Compile((MSSQLDataContext ldb, String lip) => (from q in ldb.Machines where q.Ip == lip select q).SingleOrDefault());
            return fmachine(db, ip);
        }

        public override void UpdateMachineState(Golem.Indexer.Data.MachineDescriptor machine)
        {
            lock (this)
            {
                using (Models.MSSQLDataContext db = new Models.MSSQLDataContext(this.ConnectionString))
                {
                    var fmachine = GetMachine(db, machine.IP);

                    if (fmachine == null)
                    {
                        fmachine = new Machine();
                        db.Machines.InsertOnSubmit(fmachine);
                    }

                    fmachine.IsOnline = machine.IsOnline;
                    fmachine.Ip = machine.IP;
                    fmachine.LastUpdateTime = machine.LastUpdateTime;
                    fmachine.Hostname = (String.IsNullOrEmpty(machine.Hostname)) ? String.Empty : machine.Hostname;

                    db.SubmitChanges(ConflictMode.ContinueOnConflict);
                }
            }
        }

        public override void WriteFiles(Golem.Indexer.Data.MachineDescriptor machine, Golem.Indexer.Data.Protocol protocol, List<Golem.Indexer.Data.FileDescriptor> files)
        {
            lock (this)
            {
                var protocolName = Enum.GetName(typeof(Golem.Indexer.Data.Protocol), protocol);
                long protocolId = 0;

                using (Models.MSSQLDataContext db = new Models.MSSQLDataContext(this.ConnectionString))
                {
                    var fmachine = GetMachine(db, machine.IP);
                    protocolId = this.GetProtocol(db, fmachine.Id, protocolName).Id;

                    var dbFiles = CreateDbFiles(protocolId, fmachine.Id, files);

                    db.Files.InsertAllOnSubmit(dbFiles);
                    db.SubmitChanges(ConflictMode.ContinueOnConflict);
                }
            }
        }

        private static List<Models.File> CreateDbFiles(long protocolId, long machineId, List<Golem.Indexer.Data.FileDescriptor> files)
        {
            List<Models.File> dbFiles = new List<File>();

            Parallel.ForEach(files, file =>
            {
                Models.File f = new File();
                f.FullPath = HttpUtility.UrlDecode(file.FullName);
                f.Name = file.Name;
                f.Size = file.Size;
                f.Type = Enum.GetName(typeof(Golem.Indexer.Data.FileType), file.Type);
                f.ProtocolId = protocolId;
                f.NewData = true;
                f.MachineId = machineId;

                lock (dbFiles)
                {
                    dbFiles.Add(f);
                }
            });

            return dbFiles;
        }

        public override void CreateProtocol(Golem.Indexer.Data.MachineDescriptor machine, Golem.Indexer.Data.Protocol protocol)
        {
            lock (this)
            {
                using (Models.MSSQLDataContext db = new Models.MSSQLDataContext(this.ConnectionString))
                {
                    var fmachine = GetMachine(db, machine.IP);
                    var protocolName = Enum.GetName(typeof(Golem.Indexer.Data.Protocol), protocol);

                    var dbProtocol = this.GetProtocol(db, fmachine.Id, protocolName);

                    if (dbProtocol == null)
                    {
                        db.Protocols.InsertOnSubmit(new Protocol() { MachineId = fmachine.Id, Name = protocolName, LastUpdateTime = DateTime.Now });
                        db.SubmitChanges(ConflictMode.ContinueOnConflict);
                    }
                }
            }
        }

        public override void DoPermanentProtocolData(Golem.Indexer.Data.MachineDescriptor machine, Golem.Indexer.Data.Protocol protocol)
        {
            lock (this)
            {
                using (Models.MSSQLDataContext db = new Models.MSSQLDataContext(this.ConnectionString))
                {
                    db.CommandTimeout = 1200000;
                    var fmachine = GetMachine(db, machine.IP);
                    var protocolName = Enum.GetName(typeof(Golem.Indexer.Data.Protocol), protocol);

                    var dbProtocol = this.GetProtocol(db, fmachine.Id, protocolName);

                    if (dbProtocol != null)
                    {
                        db.RemoveProtocol(dbProtocol.Id);

                        dbProtocol.FilesCount = dbProtocol.Files.Count;
                        dbProtocol.Size = dbProtocol.Files.Sum(m => m.Size);

                        db.SubmitChanges(ConflictMode.ContinueOnConflict);
                    }
                }
            }
        }

        public override void Close()
        {
            
        }

        public override data.SearchResult DoFulltextSearch(string search, IEnumerable<string> types = null)
        {
            using (Models.MSSQLDataContext db = new MSSQLDataContext(this.ConnectionString))
            {
                db.Log = new System.IO.StreamWriter("D:\\tsql.log");

                IQueryable<SearchResult> output;
                if (types != null && types.Count() > 0)
                {
                    output = (from q in db.Search(search) where types.Contains(q.Type) select q).ToList().AsQueryable();
                }
                else
                {
                    output = (from q in db.Search(search) select q).ToList().AsQueryable();
                }
                
                var files = new List<data.FileDescriptor>();
                HashSet<long> machineIds = new HashSet<long>();

                foreach (var o in output)
                {
                    files.Add(new Golem.Indexer.Data.FileDescriptor(o.Name, o.FullPath, o.Size, (Golem.Indexer.Data.FileType)Enum.Parse(typeof(Golem.Indexer.Data.FileType), o.Type), o.MachineId));
                    machineIds.Add(o.MachineId);
                }

                var machines = (from q in db.Machines where machineIds.ToList().Contains(q.Id) select new data.MachineDescriptor(q.Ip) { Id = q.Id, Hostname = q.Hostname, IsOnline = q.IsOnline, LastUpdateTime = q.LastUpdateTime}).ToDictionary(i => i.Id);
                db.Log.Close();
                db.Log = null;
                return new data.SearchResult(machines, files);
            }
        }
    }
}
